iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Modern Web

web3 短篇集系列 第 23

UUPS Proxy Pattern (ERC-1822)

  • 分享至 

  • xImage
  •  

UUPS 是可升級合約的一種實作方式,它的特色是將 upgrade 函式寫在邏輯合約,合約升級的時候也會一併調整下次升級的權限機制。

代理合約通常需要設計兩個功能:

  • 一個 storage slot 儲存邏輯合約的地址
  • 一個權限管理機制能夠授權升級

ERC-1967 只規範儲存邏輯合約的位置,至於如何更換邏輯合約的地址,則交由不同種類的可升級合約去實作。

UUPS 的特色是將升級函式寫在邏輯合約,TransparentProxy 則是將升級函式寫在代理合約。

升級函式位於邏輯合約,升級的時候,是呼叫「代理合約」的 upgrade,然後再 delegatecall 到邏輯合約的 upgrade

UUPS 的好處是,升級時 upgrade 的邏輯也能一起升級,舉例來說,原本由一個 EOA 來掌權升級,升級之後,下次變成需要多把 EOA 才能升級,當然也可能反過來從複雜變簡單的機制。

UUPS 一體兩面的危險是,升級機制沒寫好,可能導致之後無法升級。

UUPS 規定在邏輯合約實作 proxiableUUID,它會回傳邏輯合約地址的儲存位置,若遵循 ERC-1967,storage slot 的位置就會是 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc,來自 keccak256("eip1967.proxy.implementation") - 1

proxiableUUID 是為了確保升級時,新的邏輯合約也有將邏輯合約地址儲存在同一個 storage slot。

以下為 Openzeppelin 的程式碼片段:

function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }

每個邏輯合約都有的升級函式,OZ 取名 upgradeToAndCall

function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

其中,_authorizeUpgrade 是開發者要自行實作的部分,用於設計更換邏輯地址的權限。

--

以下是暫時的實作結果,還沒設計升級權限,這會導致每個人都可以升級合約,是最危險的情況,通常要為 _authorizeUpgrade 加上 onlyOwner。但我們不能直接引用 Ownable.sol 在邏輯合約,因為我們不能在邏輯合約寫 constructor,明天再來介紹 Initializable 的概念。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

contract UUPSProxy is ERC1967Proxy {
    constructor(address _implementation, bytes memory _data) payable ERC1967Proxy(_implementation, _data) {}
}

contract ImplOne is UUPSUpgradeable {
    function _authorizeUpgrade(address newImplementation) internal override {}

    function myNumber() public pure returns (uint256) {
        return 1;
    }
}

contract ImplTwo is UUPSUpgradeable {
    function _authorizeUpgrade(address newImplementation) internal override {}

    function myNumber() public pure returns (uint256) {
        return 2;
    }
}

Reference


上一篇
代理合約的統一介面 (ERC-1967)
下一篇
可升級合約的初始化
系列文
web3 短篇集30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言